﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Xml;
using Notoric.Model.Collections;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;

namespace Notoric.Model.Elements
{
    /// <summary>
    /// Implements a part.
    /// </summary>
    public class PartElement : ModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="PartElement"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="MeasureElement"/>
        /// </para>
        /// <para>
        /// The measure that directly owns this part.
        /// </para>
        /// </param>
        /// <param name="descriptor">
        /// <para>
        /// Type: <see cref="PartDescriptorElement"/>
        /// </para>
        /// <para>
        /// The descriptor of this part.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal PartElement(CompositionPart packagePart, MeasureElement owner, PartDescriptorElement descriptor)
            : base(packagePart, owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");
            if (descriptor == null)
                throw new ArgumentNullException("descriptor");

            this.descriptor = descriptor;
            descriptorIdAttribute.Value = descriptor.ID.ToString();

            voicesCollection = new ElementCollection<VoiceDescriptorElement>(this, voicesElement);
            voices = new ReadOnlyCollection<VoiceDescriptorElement>(voicesCollection);
        }

        /// <summary>
        /// Creates a new <see cref="PartElement"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="MeasureElement"/>
        /// </para>
        /// <para>
        /// The measure that directly owns this part.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal PartElement(CompositionPart packagePart, MeasureElement owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            descriptorIdAttribute = element.Attributes[PackageVocabulary.PartDescriptorAttribute];
            var dId = new Guid(descriptorIdAttribute.Value);
            descriptor = FindOwner<CompositionElement>().Parts.First(d => d.ID == dId);

            voicesElement = element[PackageVocabulary.VoiceDescriptorElementName + "s", PackageVocabulary.PackageNamespaceUri];
            voicesCollection = new ElementCollection<VoiceDescriptorElement>(this, voicesElement);
            voices = new ReadOnlyCollection<VoiceDescriptorElement>(voicesCollection);

            foreach (VoiceDescriptorElement voice in voicesCollection)
            {
                voice.PropertyChanged += new PropertyChangedEventHandler(voice_PropertyChanged);
                if (longestVoice == null || longestVoice.SymbolsCount < voice.SymbolsCount)
                    longestVoice = voice;
                if (shortestDuration == 0 || (voice.ShortestDuration < shortestDuration && voice.ShortestDuration > 0))
                    shortestDuration = voice.ShortestDuration;
            }

            content = new MusicalContentElement(packagePart, this, element[PackageVocabulary.MusicalContentElementName, PackageVocabulary.PackageNamespaceUri]);
        }

        #endregion

        #region ModelElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateObjectElement(XmlDocument document)
        {
            var element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.PartElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            descriptorIdAttribute = document.CreateAttribute(PackageVocabulary.PartDescriptorAttribute);
            element.Attributes.Append(descriptorIdAttribute);

            voicesElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.VoiceDescriptorElementName + "s",
                PackageVocabulary.PackageNamespaceUri
                );
            element.AppendChild(voicesElement);

            content = new MusicalContentElement((CompositionPart)PackagePart, this);
            element.AppendChild(content.Element);

            return element;
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when the value of some property of a voice descriptor has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// <para>
        /// If the modified property is <see cref="VoiceDescriptorElement.SymbolsCount"/> the handler checks
        /// if the <paramref name="sender"/> has not became the <see cref="LongestVoice"/>. If so, it updates
        /// the value of the <see cref="LongestVoice"/> property and raises the <see cref="ModelElement.PropertyChanged"/> event.
        /// </para>
        /// <para>
        /// If the modified property is <see cref="VoiceDescriptorElement.ShortestDuration"/> the handler checks
        /// if the <paramref name="sender"/>'s <see cref="VoiceDescriptorElement.ShortestDuration"/> has not became the 
        /// <see cref="ShortestDuration"/>. If so, it updates the value of the <see cref="ShortestDuration"/> property
        /// and raises the <see cref="ModelElement.PropertyChanged"/> event.
        /// </para>
        /// </remarks>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void voice_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "SymbolsCount")
            {
                var voice = (VoiceDescriptorElement)sender;

                if (longestVoice == null || voice.SymbolsCount > longestVoice.SymbolsCount)
                {
                    longestVoice = voice;
                    RaisePropertyChanged("LongestVoice");
                }
            }
            else if (e.PropertyName == "ShortestDuration")
            {
                var voice = (VoiceDescriptorElement)sender;

                if (shortestDuration == 0 || (voice.ShortestDuration < shortestDuration && voice.ShortestDuration > 0))
                    shortestDuration = voice.ShortestDuration;

                RaisePropertyChanged("ShortestDuration");
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Finds a voice descriptor that corresponds to a given voice or creates it.
        /// </summary>
        /// <param name="voice">
        /// <para>
        /// Type: <see cref="Byte"/>
        /// </para>
        /// <para>
        /// The voice whose descriptor is being searched.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="VoiceDescriptorElement"/>
        /// </para>
        /// <para>
        /// The descriptor of the voice.
        /// </para>
        /// </returns>
        public VoiceDescriptorElement GetVoiceDescriptor(byte voice)
        {
            var descriptor = voicesCollection.FirstOrDefault(vd => vd.Number == voice);
            if (descriptor == null)
            {
                descriptor = new VoiceDescriptorElement((CompositionPart)PackagePart, this, voice);
                voicesCollection.Add(descriptor);

                descriptor.PropertyChanged += new PropertyChangedEventHandler(voice_PropertyChanged);
            }

            return descriptor;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the instance of the beat with a given beginning.
        /// </summary>
        /// <remarks>
        /// This property wraps the expression this.<see cref="Content"/>[beginning].
        /// </remarks>
        /// <param name="beginning">
        /// <para>
        /// Type: <see cref="System.UInt16"/>
        /// </para>
        /// <para>
        /// The beginning of the requested beat.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="BeatElement"/>
        /// </para>
        /// <para>
        /// The beat element with the given beginning or <see langword="null"/> if no such exists.
        /// </para>
        /// </returns>
        public BeatElement this[ushort beginning]
        {
            get { return content[beginning]; }
        }

        /// <summary>
        /// Gets the musical content of this part.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="MusicalContentElement"/>
        /// </para>
        /// <para>
        /// The musical content of this part.
        /// </para>
        /// </value>
        public MusicalContentElement Content
        {
            get { return content; }
        }

        /// <summary>
        /// Gets the part descriptor of this part.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="PartDescriptorElement"/>
        /// </para>
        /// <para>
        /// The descriptor of this part.
        /// </para>
        /// </value>
        public PartDescriptorElement Descriptor
        {
            get { return descriptor; }
        }

        /// <summary>
        /// Gets the longest voice in this part.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="VoiceDescriptorElement"/>
        /// </para>
        /// <para>
        /// A reference to the longest voice (the one that contains the most of the symbols) in this part.
        /// This value is <see langword="null"/> if the part is empty.
        /// </para>
        /// </value>
        public VoiceDescriptorElement LongestVoice
        {
            get { return longestVoice; }
        }

        /// <summary>
        /// Gets the shortest duration in the part.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The shortest duration that appears in this part in this measure.
        /// This value is 0 if the part is empty.
        /// </para>
        /// </value>
        public ushort ShortestDuration
        {
            get { return shortestDuration; }
        }

        /// <summary>
        /// Gets the collection of voices in this part.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ReadOnlyCollection{T}"/> of <see cref="VoiceDescriptorElement"/>
        /// </para>
        /// <para>
        /// The collectionof voices in this part.
        /// </para>
        /// </value>
        public ReadOnlyCollection<VoiceDescriptorElement> Voices
        {
            get { return voices; }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="Content"/> property.
        /// </summary>
        private MusicalContentElement content;
        /// <summary>
        /// Holds the value of the <see cref="Descriptor"/> property.
        /// </summary>
        private PartDescriptorElement descriptor;
        /// <summary>
        /// Holds the XML attribute that holds the ID of the associated part descriptor.
        /// </summary>
        private XmlAttribute descriptorIdAttribute;
        /// <summary>
        /// Holds the value of the <see cref="LongestVoice"/> property.
        /// </summary>
        private VoiceDescriptorElement longestVoice;
        /// <summary>
        /// Holds the value of the <see cref="ShortestDuration"/> property.
        /// </summary>
        private ushort shortestDuration;
        /// <summary>
        /// Holds the value of the <see cref="Voices"/> property.
        /// </summary>
        private ReadOnlyCollection<VoiceDescriptorElement> voices;
        /// <summary>
        /// Holds the collection of voices in this part.
        /// </summary>
        private ElementCollection<VoiceDescriptorElement> voicesCollection;
        /// <summary>
        /// Holds the XML element that holds the <see cref="voicesCollection"/>.
        /// </summary>
        private XmlElement voicesElement;

        #endregion
    }
}
